
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <termios.h>

#include <string.h>
#include <jni.h>

#include <android/log.h>

#define LOG_TAG "UARTJNI"


#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

#define BUF_SIZE 1024

static char rBuf[BUF_SIZE];
static char wBuf[BUF_SIZE];

static const char *kClassName = "com/easy/carpanel/uart/uartJni";

jint openNative(JNIEnv *env, jobject obj, jstring name, jint baud);
jint closeNative(JNIEnv *env, jobject obj, jint fd);
jint readNative(JNIEnv *env, jobject obj, jint fd, jbyteArray buf, jint len);
jint writeNative(JNIEnv *env, jobject obj, jint fd, jbyteArray buf, jint len);

static jint getBaud(jint baud);

/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] = {
        /* name, signature, funcPtr */
        {"open",     "(Ljava/lang/String;I)I", (void*)openNative},
        {"close",    "(I)I",                   (void*)closeNative},
        {"read",     "(I[BI)I",                (void*)readNative},
        {"write",    "(I[BI)I",                (void*)writeNative}
};

jint openNative(JNIEnv *env, jobject obj, jstring name, jint baud)
{
    int fd = -1;
    struct termios ti;
    const char *device = (*env)->GetStringUTFChars(env, name, NULL);


    fd = open(device, O_RDWR | O_NOCTTY /*| O_NONBLOCK*/ | O_NDELAY);
    if (fd < 0) {
        LOGE("Can't open serial port: %s (%d)", strerror(errno),  errno);
        goto ERR;
    }

    LOGD("device is %s and fd is %d", device, fd);

    //tcflush(fd, TCIOFLUSH);

    if (tcgetattr(fd, &ti) < 0) {
        LOGE("Can't get port settings: %s (%d)", strerror(errno), errno);
        close(fd);
        goto ERR;
    }

    cfmakeraw(&ti);

    //ti.c_cflag |= CLOCAL;
    //ti.c_cflag |= CRTSCTS;

    cfsetispeed(&ti, getBaud(baud));
    cfsetospeed(&ti, getBaud(baud));

    if (tcsetattr(fd, TCSANOW, &ti) < 0) {
        LOGE("Can't change port settings: %s (%d)", strerror(errno), errno);
        goto ERR;
    }
    //tcflush(fd, TCIOFLUSH);

    (*env)->ReleaseStringUTFChars(env, name, device);
    return fd;

    ERR:
    (*env)->ReleaseStringUTFChars(env, name, device);
    return -1;
}

jint closeNative(JNIEnv *env, jobject obj, jint fd)
{
    int ret = -1;
    LOGD("close device");
    ret = close(fd);
    return ret;
}

jint readNative(JNIEnv *env, jobject obj, jint fd, jbyteArray buf, jint len)
{
    int length = 0;

    //LOGD("read uart");
    if (len > 0) {
        length = read(fd, rBuf, len);
    }

    if (length > 0) {
        (*env)->SetByteArrayRegion(env, buf, 0, (jsize)length, (jbyte*)rBuf);
    }
    return length;
}

jint writeNative(JNIEnv *env, jobject obj, jint fd, jbyteArray buf, jint len)
{
    int length = 0;

    //LOGD("write uart");
    if (len > 0) {
        (*env)->GetByteArrayRegion(env, buf, 0, (jsize)len, (jbyte*)wBuf);
        length = write(fd, wBuf, len);
    }

    return length;
}

static jint getBaud(jint baud)
{
    switch (baud) {
        case 4800:
            return B4800;
        case 9600:
            return B9600;
        case 19200:
            return B19200;
        case 38400:
            return B38400;
        case 57600:
            return B57600;
        case 115200:
            return B115200;
        default:
            return B9600;
    }
    return B9600;
}

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv *env = NULL;
    jclass cls;

    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("current JNI not support JNI_VERSION_1_4");
        return JNI_ERR;
    }

    cls = (*env)->FindClass(env, kClassName);
    if (cls == NULL) {
        LOGE("can not find class %s", kClassName);
        return JNI_ERR;
    }

    if ((*env)->RegisterNatives(env, cls, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) != JNI_OK) {
        LOGE("can not register native methods");
        return JNI_ERR;
    }

    return JNI_VERSION_1_4;
}

